#include "frame_pool.h"
#include <algorithm>
#include <sstream>

FramePool::FramePool(size_t spliceSize_, size_t spliceCnt_, size_t expandingCnt_)
	: spliceSize(spliceSize_), spliceCnt(spliceCnt_), expandingCnt(expandingCnt_)
{
	ASSERT(spliceSize_ > 0 && spliceCnt_ > 0);
	totalSize = spliceSize_ * spliceCnt_;
	pool = new char[totalSize];
	ASSERT(pool != nullptr);
	memset(pool, 0, totalSize);
}
FramePool::~FramePool()
{
	MUTEX_GUARD(mtx);
	frameList.clear();
	if (pool != nullptr)
	{
		delete pool;
	}
}
bool FramePool::Init()
{
	for (size_t i = 0; i < totalSize; i += spliceSize)
	{
		Frame* f = new Frame(*this, pool + i, spliceSize, true);
		frameList.push_back(f);
	}
	frameList.shrink_to_fit();
	return true;
}
bool FramePool::Fini()
{
	isFinished = true;
	if (!IsFull())
	{
		return false;
	}
	return true;
}
FramePtr FramePool::BorrowFrame()
{
	if (isFinished)
	{
		return nullptr;
	}
	MUTEX_GUARD(mtx);
	// borrow frame from current
	if (!frameList.empty())
	{
		Frame* f = frameList.back();
		frameList.pop_back();
		ASSERT(f != nullptr);
		return std::make_shared<FrameWrapper>(f);
	}
	// borrow frame from next
	else
	{
		// next has already allocated
		if (next != nullptr)
		{
			return next->BorrowFrame();
		}
		if (!Expanding())
		{
			return CreateOutsideFrame();
		}
		ASSERT(next != nullptr);
		return next->BorrowFrame();
	}
}
int FramePool::BorrowFrames(std::vector<FramePtr>& frameVec, size_t need, size_t needCnt)
{
	if (isFinished)
	{
		return -1;
	}
	ASSERT(need > 0 && needCnt > 0);
	MUTEX_GUARD(mtx);
	size_t curCnt = needCnt;
	size_t curOffset = 0;
	if (frameList.size() >= needCnt)
	{
		curCnt = needCnt;
	}
	else
	{
		curCnt = frameList.size();
		curOffset = needCnt - curCnt;
	}
	while (curCnt--)
	{
		Frame* f = frameList.back();
		frameList.pop_back();
		ASSERT(f != nullptr);
		frameVec.emplace_back(std::make_shared<FrameWrapper>(f));
	}
	if (curOffset == 0)
	{
		return needCnt;
	}
	// next has already allocated
	if (next != nullptr)
	{
		next->BorrowFrames(frameVec, need, curOffset);
		return frameVec.size();
	}
	
	if (Expanding())
	{
		ASSERT(next != nullptr);
		next->BorrowFrames(frameVec, need, curOffset);
		return frameVec.size();
	}
	while (curOffset--)
	{
		frameVec.emplace_back(CreateOutsideFrame());
	}
	return frameVec.size();
}
void FramePool::ReturnFrame(Frame* f)
{
	ASSERT(f != nullptr);
	MUTEX_GUARD(mtx);
	if (f->IsFromPool())
	{
		frameList.push_back(f);
		return;
	}
	FreeOutsideFrame(f);
}

bool FramePool::Expanding()
{
	// not allow expanding
	if (expandingCnt == 0)
	{
		return false;
	}
	next = new FramePool(spliceSize, spliceCnt, expandingCnt - 1);
	return next->Init();
}
FramePtr FramePool::CreateOutsideFrame()
{
	Frame* f = new Frame(*this, new char[spliceSize], spliceSize, false);
	ASSERT(f != nullptr);
	outsideCnt++;
	return std::make_shared<FrameWrapper>(f);
}

std::string FramePool::Audit() const
{
	std::stringstream ss;
	ss << "[Total Audit Info: totalRemainFrames=" << GetTotalRemainFrames()
		<< ", totalPoolCnt=" << GetTotalPoolCnt() << ", totalOutsideCnt=" << GetTotalOutsideCnt()
		<< "]" << std::endl;
	ss << AuditCurrent();
	auto tmp = next;
	while (tmp != nullptr)
	{
		ss << tmp->AuditCurrent();
		tmp = tmp->next;
	}
	return ss.str();
}
std::string FramePool::AuditCurrent() const
{
	std::stringstream ss;
	ss << "[Current Audit Info: ";
	ss << "totalSz=" << totalSize << ", spliceSz=" << spliceSize << ", spliceCnt=" << spliceCnt
		<< ", remainFrames=" << GetRemainCnt() << ", expandingCnt=" << expandingCnt;
	ss << "]" << std::endl;
	return ss.str();
}

size_t FramePool::GetTotalRemainFrames() const
{
	MUTEX_GUARD(mtx);
	if (next == nullptr)
	{
		return frameList.size();
	}
	return frameList.size() + next->GetTotalRemainFrames();
}
size_t FramePool::GetTotalPoolCnt() const
{
	MUTEX_GUARD(mtx);
	if (next == nullptr)
	{
		return 1;
	}
	return 1 + next->GetTotalPoolCnt();
}

size_t FramePool::GetTotalOutsideCnt() const
{
	MUTEX_GUARD(mtx);
	if (next == nullptr)
	{
		return outsideCnt;
	}
	return outsideCnt + next->GetTotalOutsideCnt();
}
